<?php defined('SYSPATH') or die('No direct script access.');

/**
 * HTTP Transport implemenation that uses the builtin http URL wrappers and file_get_contents
 */
class Http_Client_FileGetContents extends Http_Client
{

	/**
	 * Reusable stream context resources for GET and POST operations
	 *
	 * @var resource
	 */
	private $_get_context, $_head_context, $_post_context;

	/**
	 * Initializes our reuseable get and post stream contexts
	 */
	public function __construct()
	{
		$this->_get_context = stream_context_create();
		$this->_head_context = stream_context_create();
		$this->_post_context = stream_context_create();
	}

	public function perform_get_request($url, $timeout = false)
	{
		// set the timeout if specified
		if ($timeout !== FALSE && $timeout > 0.0)
		{
			// timeouts with file_get_contents seem to need
			// to be halved to work as expected
			$timeout = (float) $timeout / 2;

			stream_context_set_option($this->_get_context, 'http', 'timeout', $timeout);
		}
		else
		{
			// use the default timeout pulled from default_socket_timeout otherwise
			stream_context_set_option($this->_get_context, 'http', 'timeout', $this->get_default_timeout());
		}

		// $http_response_headers will be updated by the call to file_get_contents later
		// see http://us.php.net/manual/en/wrappers.http.php for documentation
		// Unfortunately, it will still create a notice in analyzers if we don't set it here
		$http_response_header = null;
		$response_body = @file_get_contents($url, false, $this->_get_context);

		return $this->_get_response_from_parts($response_body, $http_response_header);
	}

	public function perform_head_request($url, $timeout = false)
	{
		stream_context_set_option($this->_head_context, array(
				'http' => array(
					// set HTTP method
					'method' => 'HEAD',

					// default timeout
					'timeout' => $this->get_default_timeout()
				)
			)
		);

		// set the timeout if specified
		if ($timeout !== FALSE && $timeout > 0.0)
		{
			// timeouts with file_get_contents seem to need
			// to be halved to work as expected
			$timeout = (float) $timeout / 2;

			stream_context_set_option($this->_head_context, 'http', 'timeout', $timeout);
		}

		// $http_response_headers will be updated by the call to file_get_contents later
		// see http://us.php.net/manual/en/wrappers.http.php for documentation
		// Unfortunately, it will still create a notice in analyzers if we don't set it here
		$http_response_header = null;
		$response_body = @file_get_contents($url, false, $this->_head_context);

		return $this->_get_response_from_parts($response_body, $http_response_header);
	}

	public function perform_post_request($url, $raw_post, $content_type, $timeout = false)
	{
		stream_context_set_option($this->_post_context, array(
				'http' => array(
					// set HTTP method
					'method' => 'POST',

					// Add our posted content type
					'header' => "Content-Type: $content_type",

					// the posted content
					'content' => $raw_post,

					// default timeout
					'timeout' => $this->get_default_timeout()
				)
			)
		);

		// set the timeout if specified
		if ($timeout !== FALSE && $timeout > 0.0)
		{
			// timeouts with file_get_contents seem to need
			// to be halved to work as expected
			$timeout = (float) $timeout / 2;

			stream_context_set_option($this->_post_context, 'http', 'timeout', $timeout);
		}

		// $http_response_header will be updated by the call to file_get_contents later
		// see http://us.php.net/manual/en/wrappers.http.php for documentation
		// Unfortunately, it will still create a notice in analyzers if we don't set it here
		$http_response_header = null;
		$response_body = @file_get_contents($url, false, $this->_post_context);

		// reset content of post context to reclaim memory
		stream_context_set_option($this->_post_context, 'http', 'content', '');

		return $this->_get_response_from_parts($response_body, $http_response_header);
	}

	private function _get_response_from_parts($raw_response, $http_headers)
	{
		//Assume 0, false as defaults
		$status = 0;
		$content_type = false;

		//iterate through headers for real status, type, and encoding
		if (is_array($http_headers) && count($http_headers) > 0)
		{
			//look at the first headers for the HTTP status code
			//and message (errors are usually returned this way)
			//
			//HTTP 100 Continue response can also be returned before
			//the REAL status header, so we need look until we find
			//the last header starting with HTTP
			//
			//the spec: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.1
			//
			//Thanks to Daniel Andersson for pointing out this oversight
			while (isset($http_headers[0]) && substr($http_headers[0], 0, 4) == 'HTTP')
			{
				// we can do a intval on status line without the "HTTP/1.X " to get the code
				$status = intval(substr($http_headers[0], 9));

				// remove this from the headers so we can check for more
				array_shift($http_headers);
			}

			//Look for the Content-Type response header and determine type
			//and encoding from it (if possible - such as 'Content-Type: text/plain; charset=UTF-8')
			foreach ($http_headers as $header)
			{
				// look for the header that starts appropriately
				if (strncasecmp($header, 'Content-Type:', 13) == 0)
				{
					$content_type = substr($header, 13);
					break;
				}
			}
		}

		return new Http_Response($status, $content_type, $raw_response);
	}
}